eletrotupi / tcc/ frontend/app/entry/[id].tsx80d91bc2ac14c41ba7e7ed6b4640d9d958a3b41f
8.8 KB | 318 lines Raw
1
import { useState, useEffect, use } from "react";
2
import {
3
  StyleSheet,
4
  Text,
5
  View,
6
  ScrollView,
7
  Pressable,
8
  ActivityIndicator,
9
} from "react-native";
10
import { useLocalSearchParams, router, Stack } from "expo-router";
11
import { format } from "date-fns";
12
import { ptBR } from "date-fns/locale";
13

14
import { Card, SubtleInfoCard } from "@/components/ui/Cards";
15
import { Badge } from "@/components/ui/Badge";
16
import { Button } from "@/components/ui/Button";
17
import { Col, Between, Row } from "@/components/ui/LayoutHelpers";
18
import { Section, SectionHeader } from "@/components/ui/Sections";
19
import { Theme, Colors, Spacing } from "@/constants/theme";
20
import { getMood } from "@/constants/moods";
21
import {
22
  getMoodComponent,
23
  intensityLabel,
24
  intensityToValue,
25
} from "@/constants/mood-components";
26
import { useMoodEntry, useDeleteMoodEntry } from "@/hooks";
27
import { getTimeBadge, formatMoment } from "@/lib/utils/time";
28
import { MoodComponentCard } from "@/components/ui/MoodComponentCard";
29

30
export default function MoodDetailScreen() {
31
  const { id } = useLocalSearchParams<{ id: string }>();
32
  const { data: entry, isLoading, isError } = useMoodEntry(id);
33
  const deleteMoodEntry = useDeleteMoodEntry();
34
  const [minutesLeft, setMinutesLeft] = useState(0);
35
  const [canDelete, setCanDelete] = useState(false);
36

37
  useEffect(() => {
38
    const updateTimer = () => {
39
      const moment = new Date(entry?.moment);
40
      const createdAt = new Date(entry?.moment);
41
      const msSinceCreation = Date.now() - createdAt.getTime();
42
      const deletionWindowMs = 5 * 60 * 1000;
43

44
      const newCanDelete = msSinceCreation < deletionWindowMs;
45
      const newMinutesLeft = Math.max(
46
        0,
47
        Math.ceil((deletionWindowMs - msSinceCreation) / 60000),
48
      );
49

50
      setCanDelete(newCanDelete);
51
      setMinutesLeft(newMinutesLeft);
52
    };
53

54
    updateTimer(); // Set initial values
55

56
    const interval = setInterval(updateTimer, 1000);
57

58
    return () => clearInterval(interval);
59
  }, [entry?.moment]);
60

61
  if (isLoading) {
62
    return (
63
      <View style={styles.centered}>
64
        <ActivityIndicator size="large" color={Theme.colors.tint} />
65
      </View>
66
    );
67
  }
68

69
  if (isError || !entry) {
70
    return (
71
      <View style={styles.centered}>
72
        <Text style={styles.errorText}>Registro não encontrado.</Text>
73
        <Pressable onPress={() => router.back()} style={styles.backButton}>
74
          <Text style={styles.backButtonText}>Voltar</Text>
75
        </Pressable>
76
      </View>
77
    );
78
  }
79

80
  const moment = new Date(entry.moment);
81
  const moodDef = getMood(entry.selectedMood.toLowerCase());
82
  const badge = getTimeBadge(moment);
83

84
  const handleDelete = async () => {
85
    await deleteMoodEntry.mutateAsync(String(entry.id));
86

87
    router.back();
88
  };
89

90
  const deleteButtonMsg = () => {
91
    const msg = "Excluir Registro";
92
    const timeLeftStr = `(Disponível por ${minutesLeft < 1 ? "<1" : minutesLeft}:00)`;
93
    const suffix = canDelete ? timeLeftStr : "(Expirado)";
94

95
    return `${msg} ${suffix}`;
96
  };
97

98
  return (
99
    <>
100
      <Stack.Screen options={{ title: "Ver detalhes" }} />
101

102
      <ScrollView
103
        style={styles.container}
104
        contentContainerStyle={styles.content}
105
        showsVerticalScrollIndicator={false}
106
      >
107
        {/* Header card */}
108
        <Card style={styles.headerCard}>
109
          <Between>
110
            <Row gap={12} style={{ alignItems: "center" }}>
111
              <Text style={styles.moodIcon}>{moodDef?.icon ?? "😐"}</Text>
112
              <Col gap={2}>
113
                <Text style={styles.moodLabel}>
114
                  {moodDef?.label ?? entry.selectedMood}
115
                </Text>
116
                <Text style={styles.moodTime}>{formatMoment(moment)}</Text>
117
              </Col>
118
            </Row>
119
            {/* If this is the latest mood entry, show the active badge */}
120
            <Badge
121
              label="Registro Ativo"
122
              backgroundColor={Theme.colors.activeBackground}
123
              color={Theme.colors.tint}
124
            />
125
          </Between>
126
        </Card>
127

128
        {/* Core levels */}
129
        <Section>
130
          <SectionHeader title="Níveis emocionais" variant="subtle" />
131
          {[
132
            { id: "energy", label: "Energia", value: entry.energyLevel },
133
            { id: "stress", label: "Estresse", value: entry.stressLevel },
134
            { id: "anxiety", label: "Ansiedade", value: entry.anxietyLevel },
135
          ].map((item, index, arr) => (
136
            <Card key={item.id} style={{ padding: Spacing.cardGap }}>
137
              <View key={item.label}>
138
                <View style={styles.componentRow}>
139
                  <Text style={styles.componentLabel}>{item.label}</Text>
140
                  <Text style={styles.componentIntensityGreen}>
141
                    {item.value}/10
142
                  </Text>
143
                </View>
144

145
                <View style={styles.progressTrack}>
146
                  <View
147
                    style={[
148
                      styles.progressFill,
149
                      { width: `${(item.value / 10) * 100}%` },
150
                    ]}
151
                  />
152
                </View>
153
              </View>
154
            </Card>
155
          ))}
156
        </Section>
157

158
        <Section>
159
          <SectionHeader title="Além disso, sinto..." variant="subtle" />
160
          <Col gap={0}>
161
            {entry.moodComponents.map((comp, index) => {
162
              const def = getMoodComponent(comp.component.toLowerCase());
163
              const numericIntensity =
164
                comp.intensity === "LIGHT"
165
                  ? 2
166
                  : comp.intensity === "MODERATE"
167
                    ? 5
168
                    : 8;
169

170
              return (
171
                <MoodComponentCard
172
                  key={comp.id}
173
                  id={comp.component.toLowerCase()}
174
                  intensity={comp.intensity}
175
                />
176
              );
177
            })}
178
          </Col>
179
        </Section>
180

181
        {/* Annotation / notes */}
182
        {entry.annotation && (
183
          <Section>
184
            <SectionHeader title="Anotações" variant="subtle" />
185
            <Card style={{ padding: Spacing.cardGap }}>
186
              <Text style={styles.annotation}>
187
                "{entry.annotation?.trim()}"
188
              </Text>
189
            </Card>
190
          </Section>
191
        )}
192

193
        {/* Delete */}
194
        <View style={styles.section}>
195
          <Text style={styles.sectionLabel}>GESTÃO DO REGISTRO</Text>
196
          <Button
197
            variant="danger"
198
            isLoading={deleteMoodEntry.isPending}
199
            disabled={!canDelete || deleteMoodEntry.isPending}
200
            onPress={handleDelete}
201
            title={deleteButtonMsg()}
202
          />
203

204
          <SubtleInfoCard
205
            style={styles.deleteInfo}
206
            text={`
207
              A exclusão de registros é permitida apenas nos primeiros 5 minutos
208
              após o envio para garantir a integridade do histórico emocional.
209
              `}
210
          />
211
        </View>
212
      </ScrollView>
213
    </>
214
  );
215
}
216

217
const styles = StyleSheet.create({
218
  container: {
219
    flex: 1,
220
    backgroundColor: Theme.colors.background,
221
  },
222
  content: {
223
    padding: Spacing.containerPadding,
224
    gap: Spacing.sectionGap,
225
    paddingBottom: 48,
226
  },
227
  centered: {
228
    flex: 1,
229
    justifyContent: "center",
230
    alignItems: "center",
231
    gap: 12,
232
  },
233
  errorText: {
234
    fontSize: 15,
235
    color: Colors.light.textSecondary,
236
  },
237
  backButton: {
238
    paddingHorizontal: 20,
239
    paddingVertical: 10,
240
    backgroundColor: Theme.colors.tint,
241
    borderRadius: Theme.borderRadius.md,
242
  },
243
  backButtonText: {
244
    fontWeight: "700",
245
    color: Theme.colors.text,
246
  },
247

248
  // Header
249
  headerCard: {
250
    padding: 16,
251
  },
252
  moodIcon: {
253
    fontSize: 40,
254
  },
255
  moodLabel: {
256
    fontSize: 17,
257
    fontWeight: "700",
258
    color: Colors.light.text,
259
  },
260
  moodTime: {
261
    fontSize: 13,
262
    color: Colors.light.textSecondary,
263
  },
264

265
  // Sections
266
  section: {
267
    gap: 8,
268
  },
269
  sectionLabel: {
270
    fontSize: 11,
271
    fontWeight: "600",
272
    letterSpacing: 0.8,
273
    color: Colors.light.textSecondary,
274
    textTransform: "uppercase",
275
  },
276

277
  // Components
278
  componentRow: {
279
    flexDirection: "row",
280
    justifyContent: "space-between",
281
    alignItems: "center",
282
    paddingVertical: 10,
283
  },
284
  componentLabel: {
285
    fontSize: 15,
286
    fontWeight: "700",
287
    color: Colors.light.text,
288
  },
289
  componentIntensity: {
290
    fontSize: 13,
291
    color: Colors.light.textSecondary,
292
  },
293
  componentIntensityGreen: {
294
    fontSize: 13,
295
    fontWeight: "700",
296
    color: Theme.colors.tint,
297
  },
298
  progressTrack: {
299
    height: 6,
300
    backgroundColor: Theme.colors.light.divider,
301
    borderRadius: Theme.borderRadius.full,
302
    marginBottom: 4,
303
    overflow: "hidden",
304
  },
305
  progressFill: {
306
    height: "100%",
307
    backgroundColor: Theme.colors.light.tint,
308
    borderRadius: Theme.borderRadius.full,
309
  },
310
  // Annotation
311
  annotation: {
312
    fontSize: 14,
313
    lineHeight: 22,
314
    color: Colors.light.textSecondary,
315
    fontStyle: "italic",
316
    padding: 4,
317
  },
318
});